Skip to content

MT-22401: Add email campaigns commands - #9

Open
Rabsztok wants to merge 3 commits into
mainfrom
MT-22401-cli-email-campaigns
Open

MT-22401: Add email campaigns commands#9
Rabsztok wants to merge 3 commits into
mainfrom
MT-22401-cli-email-campaigns

Conversation

@Rabsztok

@Rabsztok Rabsztok commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Draft until the server-side Email Campaigns API changes are released.

Motivation

MT-22401

Port the Email Campaigns public API (MT-21113) to the CLI.

Changes

  • Add mailtrap email-campaigns with 12 subcommands: list (--per-page/--search/--token), get, create, update (PATCH semantics — only changed flags are sent), delete, the five lifecycle actions (start, schedule --datetime, cancel, terminate, reset), and stats (--start-date/--end-date)
  • Requests follow the published OpenAPI contract: flat bodies, data-envelope unwrapping, integer --domain-id (matching the ids returned by the Sending Domains endpoints), nested reply_to/template_attributes/delivery_options built from flat flags, audience id slices
  • The endpoint is token-scoped, so these commands don't require a configured account id (unlike other resource groups)
  • README command-table row + examples under "Manage resources"

How to test

  • mailtrap email-campaigns create --name "Test" --domain-id <id> --from-local-part news --subject "Hi" with a real token — expect a draft campaign row
  • Walk the lifecycle: schedule --datetime <future ISO>cancelstartterminate; verify state transitions and that invalid transitions print the API error
  • email-campaigns list --search <name> and stats <id> --start-date ... --end-date ... return sensible tables and --output json parses

Summary by CodeRabbit

  • New Features
    • Added comprehensive email campaign management to the CLI.
    • Create, view, list, update, and delete campaigns.
    • Start, schedule, cancel, terminate, and reset campaigns.
    • View campaign performance statistics with optional date filters.
    • Configure campaign content, sender details, targeting, delivery settings, and output formats.
  • Documentation
    • Added email campaign usage examples and command reference documentation.
  • Tests
    • Added coverage for campaign management, lifecycle actions, statistics, output formats, and validation.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75183c50-9a9a-427b-ae34-ff67bff8eb28

📥 Commits

Reviewing files that changed from the base of the PR and between 5ccc323 and d1ec54c.

📒 Files selected for processing (2)
  • internal/commands/emailcampaigns/emailcampaigns_test.go
  • internal/commands/emailcampaigns/update.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/commands/emailcampaigns/emailcampaigns_test.go

📝 Walkthrough

Walkthrough

Adds a new email-campaigns CLI command group with campaign listing, retrieval, creation, updates, deletion, lifecycle actions, scheduling, statistics, tests, root-command registration, and README usage documentation.

Changes

Email campaigns

Layer / File(s) Summary
Campaign contracts and read commands
internal/commands/emailcampaigns/emailcampaigns.go, internal/commands/emailcampaigns/list.go, internal/commands/emailcampaigns/get.go, internal/commands/emailcampaigns/emailcampaigns_test.go
Defines token-scoped campaign endpoints, response models, table columns, and list/get commands with table and JSON output coverage.
Campaign creation and updates
internal/commands/emailcampaigns/attributes.go, internal/commands/emailcampaigns/create.go, internal/commands/emailcampaigns/update.go, internal/commands/emailcampaigns/emailcampaigns_test.go
Adds shared campaign flags and conditional nested payload construction for create POST requests and update PATCH requests, including required-flag, omission, audience-clearing, and conflict tests.
Campaign operations and statistics
internal/commands/emailcampaigns/delete.go, internal/commands/emailcampaigns/start.go, internal/commands/emailcampaigns/cancel.go, internal/commands/emailcampaigns/terminate.go, internal/commands/emailcampaigns/reset.go, internal/commands/emailcampaigns/schedule.go, internal/commands/emailcampaigns/stats.go, internal/commands/emailcampaigns/emailcampaigns_test.go
Adds deletion, lifecycle transitions, scheduling with datetime payloads, and date-filtered count/rate statistics commands with endpoint and output tests.
CLI integration and documentation
cmd/root.go, README.md
Registers the email campaigns command group in the root CLI and documents campaign commands and examples.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant EmailCampaignCommands
  participant APIClient
  participant EmailCampaignAPI
  participant Output
  CLI->>EmailCampaignCommands: execute campaign command
  EmailCampaignCommands->>APIClient: create authenticated client
  APIClient->>EmailCampaignAPI: request campaign resource or action
  EmailCampaignAPI-->>APIClient: campaign or statistics response
  APIClient-->>EmailCampaignCommands: decoded response
  EmailCampaignCommands->>Output: render configured output
  Output-->>CLI: formatted result
Loading

Suggested reviewers: leonid-shevtsov, vladimirtaytor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding email campaigns commands to the CLI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Rabsztok
Rabsztok marked this pull request as ready for review July 30, 2026 12:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/commands/emailcampaigns/list.go`:
- Line 121: Update the perPage flag binding in the campaign list command to use
50 as its default, matching the advertised help text, while preserving the
existing Changed check and surrounding behavior.

In `@internal/commands/emailcampaigns/start.go`:
- Around line 14-33: Update the API request flow in newLifecycleCmd and the
underlying internal/client.Client configuration so Email Campaigns calls cannot
block indefinitely. Replace the unbounded context.Background() request context
with an appropriate deadline or configure the HTTP client with a suitable
timeout, preserving the existing campaignPath and response handling.

In `@internal/commands/emailcampaigns/update.go`:
- Around line 29-32: Update the email-campaign update flow around
buildAttributesBody and the Patch call to validate that at least one mutable
attribute was provided; return a validation error before issuing Patch when the
body contains no changes. Add a regression test covering an update with only
--id and verify no request is sent.

In `@README.md`:
- Line 129: Update the README schedule command’s --datetime value to a future or
clearly placeholder timestamp, and ensure the corresponding Mailtrap app
examples include a matching schedule example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b5236ce-3368-40c0-9482-c3adc59cede6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2f517 and de151d1.

📒 Files selected for processing (16)
  • README.md
  • cmd/root.go
  • internal/commands/emailcampaigns/attributes.go
  • internal/commands/emailcampaigns/cancel.go
  • internal/commands/emailcampaigns/create.go
  • internal/commands/emailcampaigns/delete.go
  • internal/commands/emailcampaigns/emailcampaigns.go
  • internal/commands/emailcampaigns/emailcampaigns_test.go
  • internal/commands/emailcampaigns/get.go
  • internal/commands/emailcampaigns/list.go
  • internal/commands/emailcampaigns/reset.go
  • internal/commands/emailcampaigns/schedule.go
  • internal/commands/emailcampaigns/start.go
  • internal/commands/emailcampaigns/stats.go
  • internal/commands/emailcampaigns/terminate.go
  • internal/commands/emailcampaigns/update.go

Comment thread internal/commands/emailcampaigns/list.go Outdated
Comment on lines +14 to +33
func newLifecycleCmd(f *cmdutil.Factory, action, short string) *cobra.Command {
var campaignID string

cmd := &cobra.Command{
Use: action,
Short: short,
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmdutil.RequireFlag("id", campaignID); err != nil {
return err
}

c, err := f.NewClient()
if err != nil {
return err
}

var resp campaignResponse
if err := c.Post(context.Background(), client.BaseGeneral, campaignPath(campaignID, action), nil, &resp); err != nil {
return err
}

@coderabbitai coderabbitai Bot Jul 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether internal/client.Client sets a default request/dial timeout.
ast-grep outline internal/client --items all
rg -n -A5 -B5 'Timeout' internal/client

Repository: mailtrap/mailtrap-cli

Length of output: 1158


🏁 Script executed:

#!/bin/bash
set -u
printf '--- client.go ---\n'
cat -n internal/client/client.go
printf '\n--- relevant command timeout context usages ---\n'
rg -n 'context\.Background|context\.With|Timeout|Deadline|ctx' internal/client internal/commands/emailcampaigns

Repository: mailtrap/mailtrap-cli

Length of output: 10909


🌐 Web query:

Go net/http New default http.Client Timeout documentation

💡 Result:

The Go net/http package does not impose a default timeout on the standard http.Client [1][2]. The http.DefaultClient is initialized with a zero value for its Timeout field, which explicitly means "no timeout" [1][3]. In the Go documentation, the Timeout field of the http.Client struct is defined as follows [1][4]: Timeout specifies a time limit for requests made by this Client. The timeout includes connection time, any redirects, and reading the response body. A Timeout of zero means no timeout. Because http.DefaultClient has no timeout, using it or the convenience functions that rely on it (such as http.Get, http.Post, and http.Head) is generally discouraged for production applications, especially those interacting with the public internet, as it can lead to resource leaks and hung requests [5][6]. Developers are encouraged to define a custom http.Client with a specified Timeout or to use context.Context with requests to enforce deadlines [7][5].

Citations:


Add timeout coverage for context.Background() API calls.

internal/client.Client creates &http.Client{} with no Timeout, and these commands pass context.Background() by value, so Email Campaigns requests can block indefinitely if the API response hangs. Add a request deadline or configure the HTTP client with an appropriate timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/commands/emailcampaigns/start.go` around lines 14 - 33, Update the
API request flow in newLifecycleCmd and the underlying internal/client.Client
configuration so Email Campaigns calls cannot block indefinitely. Replace the
unbounded context.Background() request context with an appropriate deadline or
configure the HTTP client with a suitable timeout, preserving the existing
campaignPath and response handling.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shared internal/client.Client predates this PR and every existing command has the same behavior, so a timeout belongs in a dedicated change to the shared client rather than this campaigns PR. Leaving this thread open as a note for that follow-up.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rabsztok, agreed. The timeout concern applies to the shared internal/client.Client, not specifically to Email Campaigns. It should be evaluated in a dedicated change that defines the CLI-wide timeout behavior.

Would you like me to create a follow-up GitHub issue for this work?


✏️ Learnings added
Learnt from: Rabsztok
Repo: mailtrap/mailtrap-cli PR: 9
File: internal/commands/emailcampaigns/start.go:14-33
Timestamp: 2026-08-07T10:00:38.817Z
Learning: In the mailtrap/mailtrap-cli repository, request timeout behavior is shared by `internal/client.Client` and applies to all existing commands. Do not require a command-specific timeout change in a feature PR such as `internal/commands/emailcampaigns`; track shared client timeout changes in a dedicated follow-up.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread internal/commands/emailcampaigns/update.go
Comment thread README.md Outdated
@Rabsztok

Copy link
Copy Markdown
Contributor Author

Addressed CodeRabbit findings in 06ed4f5:

Fixed

  • list.go per-page default: bound the flag default to 50, matching the API default from the OpenAPI spec; the Changed check is kept so per_page is only sent when set explicitly, and Cobra now displays an accurate (default 50).
  • README schedule example: replaced the past 2026-06-01T09:00:00Z with an obviously-future placeholder 2030-01-01T09:00:00Z.

Skipped

  • start.go timeout for context.Background(): matches repo convention — no command in the CLI uses timeout contexts and the shared HTTP client has no timeout, so this is not campaigns-specific; a CLI-wide timeout belongs in a separate change.
  • update.go rejecting empty updates: sibling update commands (templates, webhooks) also issue PATCH with an empty partial body when no attribute flag is given; rejecting only for campaigns would diverge from established behavior.

go build ./... && go vet ./... && go test ./... all pass.

@Rabsztok

Copy link
Copy Markdown
Contributor Author

Follow-up from a cross-SDK contract audit: the API treats audience ids as the full set (explicit [] clears the audience), but pflag can't parse an empty --contact-list-ids= value, so clearing was inexpressible from the CLI. Added --clear-contact-lists / --clear-contact-segments to update (mutually exclusive with the ids flags), with tests asserting [] is sent and the conflict errors out. README example added.

@Rabsztok
Rabsztok marked this pull request as draft July 31, 2026 11:56
@Rabsztok
Rabsztok force-pushed the MT-22401-cli-email-campaigns branch from 507c382 to 13e6844 Compare August 6, 2026 12:04
Decisions:
- Request bodies are flat (no email_campaign wrapper); nested reply_to/template_attributes are assembled from flat flags
- Single, list, and stats responses unwrap the data envelope; list pagination metadata is ignored for output
- Endpoint is token-scoped (/api/email_campaigns) — no account-id requirement, unlike other resources
- Lifecycle actions (start/schedule/cancel/terminate/reset) are subcommands sharing one body-less POST helper
- Delete handles 204 No Content with a confirmation message per repo convention
@Rabsztok
Rabsztok force-pushed the MT-22401-cli-email-campaigns branch from 13e6844 to 5ccc323 Compare August 7, 2026 06:21
@Rabsztok
Rabsztok marked this pull request as ready for review August 7, 2026 07:24
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/commands/emailcampaigns/attributes.go`:
- Around line 52-53: The campaign sending-domain flow currently uses numeric
domain_id instead of the OpenAPI mailsend_domain_id UUID string. Update the
--domain-id flag and campaignAttrs in
internal/commands/emailcampaigns/attributes.go, serialize the changed flag as
mailsend_domain_id, and update the fixtures and assertions in
internal/commands/emailcampaigns/emailcampaigns_test.go at lines 47-51 and
218-223 to use UUID strings and the new request key.

In `@internal/commands/emailcampaigns/stats.go`:
- Around line 13-47: Add message_count and reject_count fields to CampaignStats
with matching JSON tags, then include both fields in campaignStatsColumns so the
decoded API totals are exposed in table output.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f06249b3-2fbf-4e22-81a5-80e1e2a810a0

📥 Commits

Reviewing files that changed from the base of the PR and between 0597ec6 and 5ccc323.

📒 Files selected for processing (16)
  • README.md
  • cmd/root.go
  • internal/commands/emailcampaigns/attributes.go
  • internal/commands/emailcampaigns/cancel.go
  • internal/commands/emailcampaigns/create.go
  • internal/commands/emailcampaigns/delete.go
  • internal/commands/emailcampaigns/emailcampaigns.go
  • internal/commands/emailcampaigns/emailcampaigns_test.go
  • internal/commands/emailcampaigns/get.go
  • internal/commands/emailcampaigns/list.go
  • internal/commands/emailcampaigns/reset.go
  • internal/commands/emailcampaigns/schedule.go
  • internal/commands/emailcampaigns/start.go
  • internal/commands/emailcampaigns/stats.go
  • internal/commands/emailcampaigns/terminate.go
  • internal/commands/emailcampaigns/update.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • internal/commands/emailcampaigns/delete.go
  • internal/commands/emailcampaigns/reset.go
  • internal/commands/emailcampaigns/cancel.go
  • README.md
  • internal/commands/emailcampaigns/get.go
  • internal/commands/emailcampaigns/start.go
  • internal/commands/emailcampaigns/emailcampaigns.go
  • cmd/root.go
  • internal/commands/emailcampaigns/schedule.go
  • internal/commands/emailcampaigns/create.go
  • internal/commands/emailcampaigns/terminate.go
  • internal/commands/emailcampaigns/list.go

Comment thread internal/commands/emailcampaigns/attributes.go
Comment thread internal/commands/emailcampaigns/stats.go
Decisions:
- Bind --per-page default to 50 (matches API default per OpenAPI spec); Changed check kept so the param is only sent when set explicitly
- README schedule example now uses an obviously-future timestamp (2030-01-01)
- No timeout added for context.Background() calls: matches repo convention (no command uses timeouts); a CLI-wide timeout belongs in a separate change
- update with no attribute flags now fails fast with a validation error instead of sending an empty PATCH
Decisions:
- The API treats contact_list_ids/contact_segment_ids as the full set,
  so an explicit [] clears them — pflag cannot parse an empty slice
  value, so dedicated --clear-contact-lists/--clear-contact-segments
  booleans express it; mutually exclusive with the ids flags
@Rabsztok
Rabsztok force-pushed the MT-22401-cli-email-campaigns branch from 5ccc323 to d1ec54c Compare August 7, 2026 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant